package org.client.otdr;


import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;

public class Sr4731Parser {
	class StC_map {
		int revNo;
		int size;
		String name;
	};
	
	private char[] trace;
	private Integer samplingPoints = 5001;
	private Double distanceRange = 10.0;
	private Double pulseWidth = 100.0;
	private Double ior = 1.488;
	private Double bsc = -90.00;
	private Double wavelength = 1550.0;
	private Integer average = 1;
	
	public Sr4731Parser(byte[] bin) {
		parse(bin);
	}
	
	public Sr4731Parser(String filename) {
		try {
			BufferedInputStream is = new BufferedInputStream(new FileInputStream(new File(filename)));
			byte[] b = new byte[is.available()];
			is.read(b, 0, is.available());
			parse(b);
			
		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	}
	
	private void parse(byte[] b) {
		if (isSr4731(b)) {
			int blockCount = bin2short(b, 10);
			
			int bpos = bin2int(b, 6);
			int ipos = 12;
			StC_map bl = getBlockIndex(b, ipos - 6);
			for (int i = 0; i < blockCount - 1; i++) {
				StC_map prev = bl;
				ipos += bl.name.length() + 7;
				if(b[ipos] == 0) ipos++;
		        bl = getBlockIndex(b, ipos-6);
		        decode(b, prev.name, bpos);
	            bpos += bl.size;
			}

		}
		else {
			System.out.println("Not OTDR file");
		}
	}

	private void decode(byte[] b, String name, int bpos) {
		if (name.equals("DataPts")) {
			readTrace(b, bpos);
		}
		else if (name.equals("FxdParams")) {
		    readFixedParams(b, bpos);
		}
	}

	private void readFixedParams(byte[] b, int bpos) {
	    final int offset = 14;
	    int pos = offset + bpos;
	    // Unit of distance
	    StringBuffer sb = new StringBuffer();
	    sb.append((char)b[pos++]);
	    sb.append((char)b[pos++]);
	    String unit = sb.toString();
	    
	    // Wavelength
	    short wavelength = bin2short(b, pos);
	    this.setWavelength((double)wavelength/10.0);
	    pos += 2;
	    
	    // Aquisition offset
	    int aquisitionOffset = bin2int(b, pos);
	    pos += 4;
	    
	    /* Acquisition Offset Distance */
	    int aquisitionOffsetDistance = bin2int(b, pos);
	    pos += 4;
	    
	    /* Total Number of Pulse Widths Used */
	    int sptr = bin2short(b, pos);
	    pos+=2;

	    /* Pulse Widths Used */
	    int n;
	    if(sptr > 8){
	        n = 8;
	    }else{
	        n = sptr;
	    }
	    int[] pulseWidth = new int[n];
	    for(int i = 0;i < n ; i++){
	        pulseWidth[i] = bin2short(b, pos);
	        pos += 2;
	        this.setPulseWidth((double)pulseWidth[0]);
	    }
	    
	    /* Data Spacing */
	    int[] dataSpacing = new int[n];	    
	    for(int i = 0; i < n ; i++){
	        dataSpacing[i] = bin2int(b, pos);
	        pos += 4;
	    }

	    /* Number of Data Points for Each Pulse Width */
	    int[] dataPoint = new int[n];
	    for(int i = 0;i < n ; i++){
	        dataPoint[i] = bin2int(b, pos);
	        pos += 4;
	        this.setSamplingPoints(dataPoint[0]);
	    }

	    /* Group Index */
	    int ior = bin2int(b, pos);
	    pos += 4;
	    this.setIor((double)ior / 100000.0);
	    
	    /* Backscatter Coefficient */
	    short bsc = bin2short(b, pos);
	    pos += 2;
	    this.setBsc((double)bsc/10.0);
	    
	    /* Number of Averages */
	    pos += 4;

	    /* Averaging Time */
	    short average = bin2short(b, pos);
	    this.setAverage((int)average);
	    pos += 2;

	    /* Acquisition Range */
	    int range = bin2int(b, pos);
	    this.setDistanceRange((double)range / 10000.0);
	    pos += 4;

	    /* Acquisition Range Distance */
	    int distance = bin2int(b, pos);
	    pos += 4;
	    System.out.println(unit);
    }

    private void readTrace(byte[] b, int bpos) {
		int size = bin2int(b, bpos = bpos + 8); // 4
		short scaleFactor = bin2short(b, bpos = bpos + 4); // 2
		int tps = bin2short(b, bpos = bpos+ 2); //
		short sf = bin2short(b, bpos = bpos + 4);
		trace = new char[size];
		for (int i = 0; i < size; i++) {
			trace[i] = (char)bin2short(b, bpos = bpos + 2);
//			System.out.println(trace[i]);
		}
	}

	private long bin2long(byte[] b, int offset) {
		return bin2num(b, offset, 8);
	}

	private short bin2short(byte[] b, int offset) {	
		return (short)bin2num(b, offset, 2);
	}

	private long bin2num(byte[] b, int offset, int size) {
		long ret = 0;
		for (int i = 0; i < size; i++) {
			ret += (b[offset + i] & 0xff) << (i * 8);
		}
		return ret;
	}
	
	private int bin2int(byte[] b, int offset) {
		return (int)bin2num(b, offset, 4);
	}

	private String bin2str(byte[] b, int offset) {
		int i = offset;
		char c;
		String ret = "";
		while( (c = (char)b[i++]) != 0x00) {
			ret += c;
		}
		return ret;
	}
	
	private boolean isSr4731(byte[] b) {
		return bin2str(b, 0).equals("Map");
	}
	
	public char[] getTrace() {
		return trace;
	}
	
	public double[] getTraceInDB() {
	    double[] ret = new double[trace.length];
	    int index = 0;
	    for (char c : trace) {
	        ret[index++] = -(double)c / 1000.0;
	    }
	    return ret;
	}
	
	private StC_map getBlockIndex(byte[] b, int startIndex) {
		StC_map ret = new StC_map();
		ret.revNo = bin2short(b, startIndex);
		ret.size = bin2int(b, startIndex + 2);
		ret.name = bin2str(b, startIndex + 6);
		return ret;
	}

    /**
     * @return the samplingPoints
     */
    public Integer getSamplingPoints() {
        return samplingPoints;
    }

    /**
     * @param samplingPoints the samplingPoints to set
     */
    public void setSamplingPoints(Integer samplingPoints) {
        this.samplingPoints = samplingPoints;
    }

    /**
     * @return the distanceRange
     */
    public Double getDistanceRange() {
        return distanceRange;
    }

    /**
     * @param distanceRange the distanceRange to set
     */
    public void setDistanceRange(Double distanceRange) {
        this.distanceRange = distanceRange;
    }

    /**
     * @return the pulseWidth
     */
    public Double getPulseWidth() {
        return pulseWidth;
    }

    /**
     * @param pulseWidth the pulseWidth to set
     */
    public void setPulseWidth(Double pulseWidth) {
        this.pulseWidth = pulseWidth;
    }

    /**
     * @return the ior
     */
    public Double getIor() {
        return ior;
    }

    /**
     * @param ior the ior to set
     */
    public void setIor(Double ior) {
        this.ior = ior;
    }

    /**
     * @return the bsc
     */
    public Double getBsc() {
        return bsc;
    }

    /**
     * @param bsc the bsc to set
     */
    public void setBsc(Double bsc) {
        this.bsc = bsc;
    }

    /**
     * @return the wavelength
     */
    public Double getWavelength() {
        return wavelength;
    }

    /**
     * @param wavelength the wavelength to set
     */
    public void setWavelength(Double wavelength) {
        this.wavelength = wavelength;
    }

    /**
     * @return the average
     */
    public Integer getAverage() {
        return average;
    }

    /**
     * @param average the average to set
     */
    public void setAverage(Integer average) {
        this.average = average;
    }

}
